package solver;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import model.Problem;
import model.Service;
import model.Solution;
import model.SolverEnvironment;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import etm.core.configuration.EtmManager;
import etm.core.monitor.EtmMonitor;
import etm.core.monitor.EtmPoint;
import gurobi.GRBVar;

public abstract class BaseExtraLeanIPModelBuilder {
	protected static Logger log = LoggerFactory
			.getLogger(BaseExtraLeanIPModelBuilder.class);
	// protected static final EtmMonitor etmMonitor =
	// EtmManager.getEtmMonitor();

	protected StringBuilder ipModelAsString;
	protected int timeLimit;
	protected long cutoff;

	protected List<Integer> affectedServices;
	protected int AS; // number of affected services

	protected List<Integer> affectedLocations;
	protected int AL;

	protected Problem problem;
	protected Solution initial_solution;
	protected Solution current_solution;
	protected boolean verbose = true;
	protected List<Integer> selectedProcesses;
	protected List<Integer> selectedMachines;
	protected List<Integer> offendedServices;
	protected List<Integer> offendedNeighborhoods;
	protected boolean includeHardConstraints[];
	protected boolean includeSoftConstraints[];
	protected long load_cost;
	protected long balance_cost;
	protected long process_move_cost;
	protected long service_move_cost;
	protected long machine_move_cost;

	protected int solver_status = -1;

	public BaseExtraLeanIPModelBuilder(Problem problem,
			List<Integer> selectedProcesses, boolean verbose,
			int timeLimitInSeconds) {
		Locale.setDefault(Locale.ENGLISH);
		this.problem = problem;
		this.selectedProcesses = selectedProcesses;
		selectedMachines = new ArrayList<Integer>();
		offendedServices = new ArrayList<Integer>();
		offendedNeighborhoods = new ArrayList<Integer>();
		for (Integer p : selectedProcesses) {
			model.Process aProcess = problem.getProcesses().get(p);
			int s = aProcess.getService();
			if (!offendedServices.contains(s))
				offendedServices.add(s);
			Service aService = problem.getServices().get(s);
			for (Integer ds : aService.getDependsOn()) {
				if (!offendedServices.contains(ds))
					offendedServices.add(ds);
			}
			for (Integer ds : aService.getDependedBy()) {
				if (!offendedServices.contains(ds))
					offendedServices.add(ds);
			}
			for (Integer m : aProcess.getEligibleMachines()) {
				if (!selectedMachines.contains(m))
					selectedMachines.add(m);
				model.Machine aMachine = problem.getMachines().get(m);
				if (!offendedNeighborhoods.contains(aMachine.getNeighborhood()))
					offendedNeighborhoods.add(aMachine.getNeighborhood());
			}
		}
		for (Integer m : selectedMachines) {
			model.Machine aMachine = problem.getMachines().get(m);
			aMachine.clearMoveableProcesses();
			for (Integer p : selectedProcesses) {
				model.Process aProcess = problem.getProcesses().get(p);
				if (aProcess.getEligibleMachines().contains(m))
					aMachine.addMoveableProcess(p);
			}
		}
		// log.debug("offended services: " + offendedServices.size() + " "
		// + offendedServices);
		// log.debug("offended neighborhoods: " + offendedNeighborhoods);
		this.verbose = verbose;
		this.timeLimit = timeLimitInSeconds;
		includeHardConstraints = new boolean[6];
		for (int i = 0; i < includeHardConstraints.length; i++) {
			includeHardConstraints[i] = true;
		}
		includeSoftConstraints = new boolean[5];
		for (int i = 0; i < includeSoftConstraints.length; i++) {
			includeSoftConstraints[i] = true;
		}
		ipModelAsString = new StringBuilder();
		computeXvarsPerNeighborhoodService(selectedProcesses);
	}

	public void setInitialSolution(Solution solution) {
		this.initial_solution = solution;
	}

	public void setCurrentSolution(Solution solution) {
		this.current_solution = solution;
	}

	public void includeAssignmentHardConstraints(boolean b) {
		setIncludeHardConstraint(0, b);
	}

	public void includeCapacityHardConstraints(boolean b) {
		setIncludeHardConstraint(1, b);
	}

	public void includeConflictHardConstraints(boolean b) {
		setIncludeHardConstraint(2, b);
	}

	public void includeSpreadHardConstraints(boolean b) {
		setIncludeHardConstraint(3, b);
	}

	public void includeDependencyHardConstraints(boolean b) {
		setIncludeHardConstraint(4, b);
	}

	public void includeTransientHardConstraints(boolean b) {
		setIncludeHardConstraint(5, b);
	}

	public void includeLoadCostSoftConstraints(boolean b) {
		setIncludeSoftConstraint(0, b);
	}

	public void includeBalanceCostSoftConstraints(boolean b) {
		setIncludeSoftConstraint(1, b);
	}

	public void includeProcessMoveCostSoftConstraints(boolean b) {
		setIncludeSoftConstraint(2, b);
	}

	public void includeServiceMoveCostSoftConstraints(boolean b) {
		setIncludeSoftConstraint(3, b);
	}

	public void includeMachineMovedCostSoftConstraints(boolean b) {
		setIncludeSoftConstraint(4, b);
	}

	private void setIncludeHardConstraint(int i, boolean status) {
		includeHardConstraints[i] = status;
	}

	private void setIncludeSoftConstraint(int i, boolean status) {
		includeSoftConstraints[i] = status;
	}

	public long getLoad_cost() {
		return load_cost;
	}

	public long getBalance_cost() {
		return balance_cost;
	}

	public long getProcess_move_cost() {
		return process_move_cost;
	}

	public long getService_move_cost() {
		return service_move_cost;
	}

	public long getMachine_move_cost() {
		return machine_move_cost;
	}

	public void printActiveInactiveConstraints() {
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < includeHardConstraints.length; i++)
			sb.append(String.format("HC%d=%s ", i,
					includeHardConstraints[i] ? "ON" : "OFF"));
		for (int i = 0; i < includeSoftConstraints.length; i++)
			sb.append(String.format("SC%d=%s ", i,
					includeSoftConstraints[i] ? "ON" : "OFF"));
		System.out.println(sb.toString());
	}

	public String getIPModelAsString() {
		String s = ipModelAsString.toString();
		s = s.replaceAll("\\+\\s-", "-");
		return s;
	}

	private long getIPSolverComputedCost() {
		return load_cost + balance_cost + process_move_cost + service_move_cost
				+ machine_move_cost;
	}

	public abstract Solution saveToSolution();

	public abstract void updateSolution(SolverEnvironment env,
			List<Integer> selectedProcesses);

	public abstract void updateSolutionDirect(SolverEnvironment env,
			List<Integer> selectedProcesses);

	public abstract boolean solve(boolean saveLP);

	protected Map<String, List<String>> selected_XVARS_per_service_neighborhood;
	protected Map<String, List<String>> selected_XVARS_per_process_neighborhood;

	private void computeXvarsPerNeighborhoodService(
			List<Integer> selectedProcesses) {
		// EtmPoint point = etmMonitor
		// .createPoint("BaseExtraLeanIPModelBuilder:computeXvarsPerNeighborhoodService");
		selected_XVARS_per_service_neighborhood = new HashMap<String, List<String>>();
		selected_XVARS_per_process_neighborhood = new HashMap<String, List<String>>();
		for (Integer p : selectedProcesses) {
			int s_id = problem.getServiceIdForProcessId(p);
			for (Integer m : problem.getEligibleMachinesFor(p)) {
				String value = String.format("%d_%d", p, m);
				int n_id = problem.getNeighborhoodIdForMachineId(m);
				String key = String.format("%d_%d", s_id, n_id);
				if (!selected_XVARS_per_service_neighborhood.containsKey(key)) {
					selected_XVARS_per_service_neighborhood.put(key,
							new ArrayList<String>());

				}
				selected_XVARS_per_service_neighborhood.get(key).add(value);
				String key2 = String.format("%d_%d", p, n_id);
				if (!selected_XVARS_per_process_neighborhood.containsKey(key2)) {
					selected_XVARS_per_process_neighborhood.put(key2,
							new ArrayList<String>());

				}
				selected_XVARS_per_process_neighborhood.get(key2).add(value);
			}
		}
		// point.collect();
	}

	public int getSolverStatus() {
		return solver_status;
	}

	protected void generateAffectedServicesAndLocations() {
		affectedServices = new ArrayList<Integer>();
		for (int s = 0; s < problem.S; s++) {
			boolean exists = false;
			Service service = problem.getServices().get(s);
			for (Integer p : service.getProcesses()) {
				if (selectedProcesses.contains(p)) {
					exists = true;
				}
			}
			if (exists) {
				affectedServices.add(s);
			}
		}
		AS = affectedServices.size();

		affectedLocations = new ArrayList<Integer>();
		for (Integer m : selectedMachines) {
			int loc = problem.getLocationIdForMachineId(m);
			if (!affectedLocations.contains(loc)) {
				affectedLocations.add(loc);
			}
		}
		AL = affectedLocations.size();
	}

	protected long computeNonSelectedMachinesBalanceCost() {
		long sum = 0;
		current_solution.refreshBalanceCostContribution();
		for (int m = 0; m < problem.M; m++) {
			if (selectedMachines.contains(new Integer(m)))
				continue;
			sum += current_solution.balance_cost_contribution_zipped[m];
		}
		return sum;
	}

	protected long computeNonSelectedMachinesLoadCost() {
		long sum = 0;
		current_solution.refreshLoadCostContribution();
		for (int m = 0; m < problem.M; m++) {
			if (selectedMachines.contains(new Integer(m)))
				continue;
			sum += current_solution.load_cost_contribution_zipped[m];
		}
		return sum;
	}

	protected long computeNonSelectedMachinesServiceMoveCost() {
		long max = 0;
		for (int s = 0; s < problem.S; s++) {
			if (affectedServices.contains(s))
				continue;
			int m = 0;
			for (Integer p : problem.getProcessesIdsForServiceId(s)) {
				if (initial_solution.getMachineIdForProcessId(p).intValue() != current_solution
						.getMachineIdForProcessId(p).intValue())
					m++;
			}
			if (m > max)
				max = m;
		}
		return max;
	}

	public abstract double getSolverObjectiveValue();
}
